namespace Tekdev.Cryptography
{
	using System;
	using System.Security.Cryptography;
	using System.IO;
	using System.Net;

	public class RSACryptography
	{
		#region Variables
		public static RSACryptoServiceProvider _rsa;
		const int KEY_LENGTH = 4096;  //Between 384 and 16384                
		const string CONTAINER_NAME = "MyContainer";
		const string PROVIDER_NAME = "Microsoft Enhanced Cryptographic Provider v1.0";
		const int PROVIDER_TYPE = 1;  //RSA = 1, DSA = 13
		#endregion        

		#region Key Management Methods
		public static void CreatePublicPrivateKey(string privateKeyLocation, string publicKeyLocation)
		{
			CreatePublicPrivateKey(privateKeyLocation, publicKeyLocation, KEY_LENGTH, CONTAINER_NAME, PROVIDER_NAME);            
		}

		public static void CreatePublicPrivateKey(string privateKeyLocation, string publicKeyLocation, int keySize)
		{
			CreatePublicPrivateKey(privateKeyLocation, publicKeyLocation, keySize, CONTAINER_NAME, PROVIDER_NAME);
		}

		public static void CreatePublicPrivateKey(string privateKeyLocation, string publicKeyLocation, int keySize, string container, string providerName)
		{
			//Need to delete container from Csp before and after each use
			//Otherwise, it will duplicate the previous key
			DeleteKeyFromContainer(keySize, container, providerName);

			CreateParameters(keySize, container, providerName);

			//creates public and private RSA XML string
			string publicPrivateKeyXML = _rsa.ToXmlString(true);
			RSAUtilties.WriteKeyFile(publicPrivateKeyXML, privateKeyLocation);

			//creates public only RSA XML string
			string publicOnlyKeyXML = _rsa.ToXmlString(false);
			RSAUtilties.WriteKeyFile(publicOnlyKeyXML, publicKeyLocation);

			DeleteKeyFromContainer(keySize, container, providerName);
		}

		private static void DeleteKeyFromContainer(int keySize, string container, string providerName)
		{
			CreateParameters(keySize, container, providerName);
			_rsa.PersistKeyInCsp = false;
			_rsa.Clear();
		}

		private static void CreateParameters()
		{
			CreateParameters(KEY_LENGTH, PROVIDER_NAME, CONTAINER_NAME);
		}

		private static void CreateParameters(int keySize, string container, string providerName)
		{
			CspParameters cspParams = new CspParameters(PROVIDER_TYPE, PROVIDER_NAME, CONTAINER_NAME);
			cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
			_rsa = new RSACryptoServiceProvider(keySize, cspParams);
		}
		#endregion

		#region Encryption/Decryption Methods

		#region Encryption Methods
		public static string Encrypt(string expression, string publicKey)
		{
			return Encrypt(expression, publicKey, KeyLocationType.File);
		}

		public static string Encrypt(string expression, string publicKey, KeyLocationType locationType)
		{
			return Encrypt(expression, publicKey, locationType, KEY_LENGTH);
		}

		public static string Encrypt(string expression, string publicKey, KeyLocationType locationType, int keySize)
		{
			return Encrypt(expression, publicKey, locationType, keySize, CONTAINER_NAME, PROVIDER_NAME);
		}

		public static string Encrypt(string expression, string publicKey, KeyLocationType locationType, int keySize, string container, string providerName)
		{
			CreateParameters(keySize, container, providerName);
			string publicOnlyKeyXML;
			if (locationType == KeyLocationType.File)
			{
				publicOnlyKeyXML = RSAUtilties.ReadKeyFile(publicKey);
			}
			else
			{
				publicOnlyKeyXML = publicKey;
			}
			_rsa.FromXmlString(publicOnlyKeyXML);

			//reads and encrypts the message
			byte[] plainBytes = System.Text.Encoding.UTF8.GetBytes(expression);
			byte[] encryptedBytes = _rsa.Encrypt(plainBytes, false);
			return Convert.ToBase64String(encryptedBytes);
		}

		public static string Encrypt(string plainText, Uri publicKeyUri)
		{
			return Encrypt(plainText, publicKeyUri, KEY_LENGTH);
		}

		public static string Encrypt(string plainText, Uri publicKeyUri, int keySize)
		{
			return Encrypt(plainText, publicKeyUri, keySize, CONTAINER_NAME, PROVIDER_NAME);
		}
		
		public static string Encrypt(string plainText, Uri publicKeyUri, int keySize, string container, string providerName)
		{
			CreateParameters();

			WebRequest request = WebRequest.Create(publicKeyUri);
			WebResponse response = request.GetResponse();
			response.Headers.Clear();
			StreamReader reader = new StreamReader(response.GetResponseStream());
			string publicOnlyKeyXML = reader.ReadToEnd();
			_rsa.FromXmlString(publicOnlyKeyXML);
			reader.Close();

			//reads and encrypts the message
			byte[] plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
			byte[] encryptedBytes = _rsa.Encrypt(plainBytes, false);
			return Convert.ToBase64String(encryptedBytes);
		}
		#endregion

		#region Decryption Methods
		public static string Decrypt(string expression, string privateKey)
		{
			return Decrypt(expression, privateKey, KeyLocationType.File);
		}

		public static string Decrypt(string expression, string privateKey, KeyLocationType locationType)
		{
			return Decrypt(expression, privateKey, KeyLocationType.File, KEY_LENGTH);
		}

		public static string Decrypt(string expression, string privateKey, KeyLocationType locationType, int keySize)
		{
			return Decrypt(expression, privateKey, KeyLocationType.File, keySize);
		}

		public static string Decrypt(string expression, string privateKey, KeyLocationType locationType, int keySize, string container, string providerName)
		{            
			CreateParameters(keySize, container, providerName);

			byte[] encryptedBytes;
			try
			{
				encryptedBytes = Convert.FromBase64String(expression);
				string publicPrivateKeyXML;

				if (locationType == KeyLocationType.File)
				{
					publicPrivateKeyXML = RSAUtilties.ReadKeyFile(privateKey);
				}
				else
				{
					publicPrivateKeyXML = privateKey;
				}
				_rsa.FromXmlString(publicPrivateKeyXML);
			}
			catch //if not a valid XML String
			{
				throw new Exception("Error reading the private key information.");
			}

			try
			{
				//reads and decrypts the encrypted text
				byte[] plain = _rsa.Decrypt(encryptedBytes, false);
				return System.Text.Encoding.UTF8.GetString(plain);
			}
			catch  //if the key cannot decrypt the data
			{
				throw new Exception("Error decrypting data. Private key does not match the encrypted data.");
			}
		}
		#endregion

		#endregion
	}
}
